package com.hanter.downloader;

import android.content.Context;
import android.net.Uri;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.hanter.downloader.exception.ResponseException;
import com.hanter.downloader.service.DownloadForkTask;
import com.hanter.downloader.service.DownloadService;
import com.hanter.downloader.service.HttpUrlConnectionService;

import java.io.File;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 类名：Downloader <br/>
 * 描述：下载器
 * 支持断点续传，错误重置，监听进度，取消任务？
 * 设置默认下载目录
 * 创建时间：2021/09/17 18:53
 *
 * @author hanter
 * @version 1.0
 */
public class Downloader {

    private static volatile Downloader sInstance;

    private final Context context;
    private final ThreadPoolExecutor executor;
    @NonNull
    private Downloader.Config config;
    @NonNull
    private DownloadService downloadService;
    private final Delivery delivery;


    private Downloader(@NonNull Context context) {
        this.context = context.getApplicationContext();
        this.executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2 + 1);
        this.config = new Config();
        this.downloadService = new HttpUrlConnectionService(this.config);
        this.delivery = new ResponseDelivery();
    }

    public static Downloader get(@NonNull Context context) {
        if (sInstance == null) {
            synchronized (Downloader.class) {
                if (sInstance == null) {
                    sInstance = new Downloader(context);
                }
            }
        }
        return sInstance;
    }

    public void setConfig(Downloader.Config config) {
        this.config = config;
        this.downloadService.updateConfig(config);
    }

    @NonNull
    public Config getConfig() {
        return config;
    }

    public void setDownloadService(@NonNull DownloadService downloadService) {
        this.downloadService = downloadService;
        this.downloadService.updateConfig(config);
    }

    public RequestBuilder load(String url) {
        return new RequestBuilder(this, url);
    }

    private void download(Request request) {
        // 请求转化为任务
//        DownloadTask task = new DownloadTask(context, this.executor, request, delivery);
        final DownloadForkTask task = new DownloadForkTask(context, null, request, downloadService, delivery);
        this.executor.submit(new Runnable() {
            @Override
            public void run() {
                try {
//                    task.fork().get();
                    task.invoke();
                    delivery.postComplete(task.getRequest());
                } catch (CancellationException e) {
                    e.printStackTrace();
                    delivery.postCancel(task.getRequest());
                } catch (Exception e) {
                    e.printStackTrace();
                    if (e instanceof ResponseException) {
                        ResponseException responseException = (ResponseException) e;
                        delivery.postError(task.getRequest(), responseException.getCode(),
                                responseException.getMessage());
                    } else {
                        delivery.postError(task.getRequest(), new Response(ResponseException.ERR_FAILED, e));
                    }
                }
            }
        });
    }

    /**
     * 不可更改，WeakHashMap会作为键 保存 对应配置
     */
    public static class Config {
        private final int connectTimeout;
        private final int readTimeOut;
        private final int bufferSize;

        /**
         * 默认配置构造器
         */
        public Config() {
            this(8000, 8000, 4096);
        }

        public Config(int connectTimeout, int readTimeOut) {
            this(connectTimeout, readTimeOut, 4096);
        }

        public Config(int connectTimeout, int readTimeOut, int bufferSize) {
            this.connectTimeout = connectTimeout;
            this.readTimeOut = readTimeOut;
            this.bufferSize  = bufferSize;
        }

        public int getReadTimeOut() {
            return readTimeOut;
        }

        public int getConnectTimeout() {
            return connectTimeout;
        }

        public int getBufferSize() {
            return bufferSize;
        }

    }

    public static class RequestBuilder {

        private final Downloader downloader;
        private final String url;
        private String name;
        private String filePath;
        private RequestListener listener;

        public RequestBuilder(Downloader downloader, String url) {
            this.downloader = downloader;
            this.url = url;
        }

        public RequestBuilder rename(String name) {
            this.name = name;
            return this;
        }

        public RequestBuilder file(File file) {
            this.filePath = file.getAbsolutePath();
            return this;
        }

        public RequestBuilder path(String filePath) {
            this.filePath = filePath;
            return this;
        }

        public RequestBuilder uri(Uri uri) {
            // 保存到Uri，最后判断
            this.filePath = uri.toString();
            return this;
        }

        public RequestBuilder listener(RequestListener listener) {
            this.listener = listener;
            return this;
        }

        private Request build() {
            return new Request(url, filePath, listener);
        }

        public Request download() {
            Request request = build();
            this.downloader.download(request);
            return request;
        }

    }

    public static final int STATE_RUNNING = 0;
    public static final int STATE_PAUSED = 3;
    public static final int STATE_CANCELED = 4;
    public static final int STATE_FAILED = 5;
    public static final int STATE_SUCCESSFUL = 6;

    /**
     * 请求，仅消费一次，不能复用使用
     */
    public static class Request {

        @NonNull
        private final String url;
        private final String filePath;
        private final RequestListener listener;

        private final List<Future<Void>> tasks = new CopyOnWriteArrayList<>();
        private final AtomicReference<Thread> threadRef;
        public volatile long contentLength = -1L;
        private final AtomicInteger state;
        private final AtomicLong progress;

        private final Lock lock = new ReentrantLock();
        private final Condition condition = lock.newCondition();

        public Request(@NonNull String url, String filePath, RequestListener listener) {
            this.url = url;
            this.filePath = filePath;
            this.listener = listener;
            this.threadRef = new AtomicReference<>();
            this.state = new AtomicInteger(STATE_RUNNING);
            this.progress = new AtomicLong(0L);
        }

        @NonNull
        public String getUrl() {
            return url;
        }

        public String getFilePath() {
            return filePath;
        }

        public RequestListener getListener() {
            return listener;
        }

        public long incProgress(long delta) {
            return this.progress.addAndGet(delta);
        }

        public void addTask(Future<Void> task) {
            this.tasks.add(task);
        }

        @Nullable
        public Thread getThread() {
            return threadRef.get();
        }

        public void setThread(@Nullable Thread thread) {
            this.threadRef.set(thread);
        }

        public void checkPausedOrAwait() throws InterruptedException {
            lock.lock();
            try {
                if (this.state.get() == STATE_PAUSED)
                    condition.await();
            } finally {
                lock.unlock();
            }
        }

        public boolean pause() {
            if (isFinished()) {
                DebugLog.d("Request", "request was finished.");
                return false;
            }

            lock.lock();
            try {
                this.state.compareAndSet(STATE_RUNNING, STATE_PAUSED);
            } finally {
                lock.unlock();
            }
            return true;
        }

        public boolean isPaused() {
            return this.state.get() == STATE_PAUSED;
        }

        void success() {
            if (isFinished()) {
                DebugLog.d("Request", "request was finished.");
                return;
            }

            lock.lock();
            try {
                this.state.set(STATE_SUCCESSFUL);
            } finally {
                lock.unlock();
            }
        }

        void fail() {
            if (isFinished()) {
                DebugLog.d("Request", "request was finished.");
                return;
            }

            lock.lock();
            try {
                int lastState = this.state.getAndSet(STATE_FAILED);
                if (lastState == STATE_PAUSED)
                    condition.signalAll();
                stop();
            } finally {
                lock.unlock();
            }
        }

        public boolean resume() {
            if (isFinished()) {
                DebugLog.d("Request", "request was finished.");
                return false;
            }

            lock.lock();
            try {
                if (this.state.compareAndSet(STATE_PAUSED, STATE_RUNNING)) {
                    this.condition.signalAll();
                }
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            } finally {
                lock.unlock();
            }
            return true;
        }

        public boolean isCanceled() {
            return this.state.get() == STATE_CANCELED;
        }

        public boolean isFailed() {
            return this.state.get() == STATE_FAILED;
        }

        public boolean isFinished() {
            int s = this.state.get();
            return s == STATE_CANCELED || s == STATE_SUCCESSFUL || s == STATE_FAILED;
        }

        public void cancel() {
            // 对应已取消的任务，无需再同步锁
            if (isFinished()) {
                DebugLog.d("Request", "request was finished.");
                return;
            }

            DebugLog.d("Request", "cancel");

            lock.lock();
            try {
                int lastState = this.state.getAndSet(STATE_CANCELED);
                if (lastState == STATE_PAUSED)
                    condition.signalAll();
                stop();
            } finally {
                lock.unlock();
            }
        }

        private void stop() {
            // stop 前 必须检查是否 暂停状态
            Thread thread = getThread();
            if (thread != null)
                thread.interrupt();

            for (Future<Void> task : tasks) {
                tasks.remove(task);
                task.cancel(true);
            }
        }

    }

}
